﻿using Extented.UI.Core;
using Extented.UI.Core.Helpers;
using Extented.UI.Core.Native;
using Extented.UI.Core.Themes;
using Infrastructure.Utils;
using Infrastructure.Utils.Helpers;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Security;
using System.Windows;
using System.Windows.Controls;

namespace Extended.WPF.Core
{
    public class ThemeManager : DependencyObject, IThemeManager
    {
        static object locker = new object();
        public const string TraceSwitchName = "ThemeManagerTracing";
        public const string TouchDelimiter = ";";
        public const string TouchDefinition = "touch";
        internal const string MethodNameString = "MethodName";
        internal const string ThemeNameTraceString = "ThemeName";
        internal const string AssemblyNameTraceString = "AssemblyName";
        internal const string KeyTraceString = "Key";
        internal const string ObjectTraceString = "Object";
        [IgnoreDependencyPropertiesConsistencyChecker()]
        public static readonly DependencyProperty ThemeProperty;
        [IgnoreDependencyPropertiesConsistencyChecker()]
        public static readonly DependencyProperty ThemeNameProperty;
        [IgnoreDependencyPropertiesConsistencyChecker()]
        public static readonly DependencyProperty TreeWalkerProperty;
        [IgnoreDependencyPropertiesConsistencyChecker()]
        static readonly DependencyProperty TouchPaddingsProperty;
        public static readonly DependencyProperty IsTouchEnabledProperty;
        static readonly DependencyPropertyKey IsTouchEnabledPropertyKey;
        public static readonly RoutedEvent ThemeChangedEvent;
        public static readonly RoutedEvent ThemeChangingEvent;
        public static event ThemeChangingRoutedEventHandler ApplicationThemeChanging;
        public static event ThemeChangedRoutedEventHandler ApplicationThemeChanged;
        public static event ThemeChangedRoutedEventHandler ThemeChanged;
        public static event ThemeChangingRoutedEventHandler ThemeChanging;
        static BooleanSwitch traceSwitch;
        static List<string> pluginAssemblies = new List<string>();
        static bool ignoreManifest = false;
        static ThemeManager instance;
        static AssemblyLoaderProxy loader;
        static double defaultTouchPaddingScale = 2;
        public static double DefaultTouchPaddingScale
        {
            get { return defaultTouchPaddingScale; }
            set { defaultTouchPaddingScale = Math.Max(0.1, value); }
        }
        static AssemblyLoaderProxy Loader
        {
            get
            {
                if (loader == null)
                {
                    loader = AssemblyLoaderProxy.Instance;
                    loader.CompletedCallback = ThemeLoaded;
                    loader.ProgressChangedCallback = ThemeLoadingProgress;
                }
                return loader;
            }
        }
        static void ThemeLoaded(object assembly) { }
        static void ThemeLoadingProgress(double progress) { }
        [SecuritySafeCritical]
        static ThemeManager()
        {
            Type ownerType = typeof(ThemeManager);
            ThemeNameProperty = DependencyProperty.RegisterAttached("ThemeName", typeof(string), ownerType, new FrameworkPropertyMetadata(null, ThemeNamePropertyChanged));
            ThemeProperty = DependencyProperty.RegisterAttached("Theme", typeof(Theme), ownerType, new FrameworkPropertyMetadata(null, ThemePropertyChanged));
            DependencyProperty dp = TextBlock.ForegroundProperty;
            TreeWalkerProperty = DependencyProperty.RegisterAttached("TreeWalker", typeof(ThemeTreeWalker), ownerType, new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits | FrameworkPropertyMetadataOptions.AffectsMeasure, TreeWalkerChanged));
            ThemeChangedEvent = EventManager.RegisterRoutedEvent("ThemeChanged", RoutingStrategy.Direct, typeof(ThemeChangedRoutedEventHandler), ownerType);
            ThemeChangingEvent = EventManager.RegisterRoutedEvent("ThemeChanging", RoutingStrategy.Direct, typeof(ThemeChangingRoutedEventHandler), ownerType);
            TouchPaddingsProperty = DependencyProperty.RegisterAttached("TouchPaddings", typeof(List<TouchInfo>), typeof(ThemeManager), new PropertyMetadata(null));
            IsTouchEnabledPropertyKey = DependencyProperty.RegisterAttachedReadOnly("IsTouchEnabled", typeof(bool), ownerType, new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.Inherits));
            IsTouchEnabledProperty = IsTouchEnabledPropertyKey.DependencyProperty;
            traceSwitch = new BooleanSwitch(TraceSwitchName, "");
            if (!System.Windows.Interop.BrowserInteropHelper.IsBrowserHosted)
            {
                AppDomain.CurrentDomain.AssemblyLoad += new AssemblyLoadEventHandler(CurrentDomain_AssemblyLoad);
            }
        }
        public static bool GetIsTouchEnabled(DependencyObject d)
        {
            return (bool)d.GetValue(IsTouchEnabledProperty);
        }
        internal static void SetIsTouchEnabled(DependencyObject d, bool value)
        {
            d.SetValue(IsTouchEnabledPropertyKey, value);
        }
        internal static void UpdatePadding(DependencyObject d)
        {
            var treewalker = GetTreeWalker(d);
            bool isTouch = false;
            if (treewalker != null)
            {
                isTouch = treewalker.IsTouch;
            }
            UpdatePaddingsInternal(d, isTouch);
        }
        static void UpdatePaddingsInternal(DependencyObject d, bool touch)
        {
            var coll = GetTouchPaddings(d);
            if (coll == null)
                return;
            foreach (var element in coll)
                UpdatePaddingInternal(d, touch, element);
        }
        static void UpdatePaddingInternal(DependencyObject d, bool touch, TouchInfo info)
        {
            if (info == null)
                return;
            Thickness value = info.Value;
            if (touch)
            {
                value = AddThickness(value, info.TouchValue);
                value = MultiplyThickness(value, info.Scale ?? DefaultTouchPaddingScale);
            }
            SetPadding(d, info, value);
        }
        static void SetPadding(DependencyObject d, TouchInfo info, Thickness value)
        {
            DependencyProperty target = info.TargetProperty ?? GetDefaultProperty(d);
            if (target == null)
                return;
            d.SetCurrentValue(target, value);
        }
        static DependencyProperty GetDefaultProperty(DependencyObject d)
        {
            if (d is Control)
                return Control.PaddingProperty;
            if (d is Border)
                return Border.PaddingProperty;
            return null;
        }
        static Thickness AddThickness(Thickness t1, Thickness t2)
        {
            return new Thickness(t1.Left + t2.Left, t1.Top + t2.Top, t1.Right + t2.Right, t1.Bottom + t2.Bottom);
        }
        static Thickness MultiplyThickness(Thickness t, double c)
        {
            return new Thickness(t.Left * c, t.Top * c, t.Right * c, t.Bottom * c);
        }
        static void DisableTouchPaddingListening()
        {
            FrameworkElement.MarginProperty.UnOverrideMetadata(typeof(Control));
            FrameworkElement.MarginProperty.UnOverrideMetadata(typeof(Border));
        }
        static void UpdateTouchPadding(DependencyObject d)
        {
        }
        protected ThemeManager() { }
        internal static ThemeManager Instance
        {
            get
            {
                if (instance == null)
                {
                    instance = new ThemeManager();
                }
                return instance;
            }
        }
        static void EnableResourcesForLoadedAssemblies()
        {
            if (System.Windows.Interop.BrowserInteropHelper.IsBrowserHosted)
            {
                IEnumerable<string> assemblies = ApplicationHelper.GetAvailableAssemblies();
                foreach (string assemblyName in assemblies)
                {
                    if (AssemblyHelper.IsDXProductAssembly(assemblyName))
                        EnableResource(AssemblyHelper.GetShortNameWithoutVersion(assemblyName));
                }
            }
            else
            {
                IEnumerable<Assembly> assemblies = AssemblyHelper.GetLoadedAssemblies();
                foreach (Assembly assembly in assemblies)
                {
                    if (AssemblyHelper.IsDXProductAssembly(assembly))
                        EnableResource(AssemblyHelper.GetShortNameWithoutVersion(assembly));
                }
            }
        }
        static bool EnableResource(string assemblyName)
        {
            return ThemePartResourceDictionary.EnableSource(new ThemePartKeyExtension() { AssemblyName = assemblyName });
        }
        [SecuritySafeCritical]
        static void CurrentDomain_AssemblyLoad(object sender, AssemblyLoadEventArgs args)
        {
            if (AssemblyHelper.IsDXProductAssembly(args.LoadedAssembly))
            {
                EnableResource(AssemblyHelper.GetShortNameWithoutVersion(args.LoadedAssembly));
            }
            if (ThemedElementsDictionary.IsCustomThemeAssembly(args.LoadedAssembly))
            {
                ThemedElementsDictionary.ForceThemeKeysLoadingForAssembly(Theme.Default.Name, args.LoadedAssembly.FullName);
            }
        }
        internal static BooleanSwitch TraceSwitch { get { return traceSwitch; } }
        [DevExpressXpfCoreLocalizedDescription("ThemeManagerApplicationThemeName")]
        [TypeConverter(typeof(ThemeNameTypeConverter))]
        public static string ApplicationThemeName
        {
            get { return GlobalThemeHelper.Instance.ApplicationThemeName; }
            set
            {
                if (ApplicationThemeChanging != null)
                    ApplicationThemeChanging(null, new ThemeChangingRoutedEventArgs(value));
                GlobalThemeHelper.Instance.ApplicationThemeName = value;
                if (ApplicationThemeChanged != null)
                    ApplicationThemeChanged(null, new ThemeChangedRoutedEventArgs(value));
            }
        }
        static bool enableDefaultThemeLoadingCore = false;

        [DevExpressXpfCoreLocalizedDescription("ThemeManagerEnableDefaultThemeLoading")]
        public static bool EnableDefaultThemeLoading
        {
            get { return enableDefaultThemeLoadingCore; }
            set
            {
                enableDefaultThemeLoadingCore = value;
            }
        }
        [Browsable(false)]
        public static List<string> PluginAssemblies { get { return pluginAssemblies; } }
        [Browsable(false)]
        public static bool IgnoreManifest
        {
            get { return ignoreManifest; }
            set { ignoreManifest = value; }
        }
        [Browsable(false)]
        public static string ActualApplicationThemeName { get { return ApplicationThemeName; } }
        static List<TouchInfo> GetTouchPaddings(DependencyObject obj)
        {
            return (List<TouchInfo>)obj.GetValue(TouchPaddingsProperty);
        }
        static void SetTouchPaddings(DependencyObject obj, List<TouchInfo> value)
        {
            obj.SetValue(TouchPaddingsProperty, value);
        }
        internal static void AddTouchInfo(DependencyObject obj, TouchInfo value)
        {
            var coll = GetTouchPaddings(obj);
            if (coll == null)
            {
                coll = new List<TouchInfo>();
                SetTouchPaddings(obj, coll);
            }
            coll.Add(value);
            UpdatePadding(obj);
        }
        internal static void RemoveTouchInfo(DependencyObject obj, TouchInfo value)
        {
            var coll = GetTouchPaddings(obj);
            if (coll == null)
                return;
            coll.Remove(value);
        }
        public static ThemeTreeWalker GetTreeWalker(DependencyObject obj)
        {
            return (ThemeTreeWalker)obj.GetValue(TreeWalkerProperty);
        }
        static void SetTreeWalker(DependencyObject d, ThemeTreeWalker value)
        {
            d.SetValue(TreeWalkerProperty, value);
        }
        public static Theme GetTheme(DependencyObject obj)
        {
            return (Theme)obj.GetValue(ThemeProperty);
        }
        public static void SetTheme(DependencyObject obj, Theme value)
        {
            obj.SetValue(ThemeProperty, value);
        }
        [TypeConverter(typeof(ThemeNameTypeConverter))]
        public static string GetThemeName(DependencyObject obj)
        {
            return (string)obj.GetValue(ThemeNameProperty);
        }
        public static void SetThemeName(DependencyObject obj, string value)
        {
            TraceHelper.Write(TraceSwitch,
                MethodNameString, MethodInfo.GetCurrentMethod().Name,
                ThemeNameTraceString, value,
                ObjectTraceString, obj);
            obj.SetValue(ThemeNameProperty, value);
        }
        public static void AddThemeChangedHandler(DependencyObject d, ThemeChangedRoutedEventHandler handler)
        {
            DependencyObjectWrapper wrapper = new DependencyObjectWrapper(d);
            wrapper.AddHandler(ThemeChangedEvent, handler);
        }
        public static void RemoveThemeChangedHandler(DependencyObject d, ThemeChangedRoutedEventHandler handler)
        {
            DependencyObjectWrapper wrapper = new DependencyObjectWrapper(d);
            wrapper.RemoveHandler(ThemeChangedEvent, handler);
        }
        public static void AddThemeChangingHandler(DependencyObject d, ThemeChangingRoutedEventHandler handler)
        {
            DependencyObjectWrapper wrapper = new DependencyObjectWrapper(d);
            wrapper.AddHandler(ThemeChangingEvent, handler);
        }
        public static void RemoveThemeChangingHandler(DependencyObject d, ThemeChangingRoutedEventHandler handler)
        {
            DependencyObjectWrapper wrapper = new DependencyObjectWrapper(d);
            wrapper.RemoveHandler(ThemeChangingEvent, handler);
        }
        static void RaiseThemeNameChanging(DependencyObject obj, string themeName)
        {
            DependencyObjectWrapper wrapper = new DependencyObjectWrapper(obj);
            ThemeChangingRoutedEventArgs eventArgs = new ThemeChangingRoutedEventArgs(themeName) { RoutedEvent = ThemeChangingEvent };
            wrapper.RaiseEvent(eventArgs);
        }
        static void RaiseThemeNameChanged(DependencyObject obj, string themeName)
        {
            DependencyObjectWrapper wrapper = new DependencyObjectWrapper(obj);
            ThemeChangedRoutedEventArgs eventArgs = new ThemeChangedRoutedEventArgs(themeName) { RoutedEvent = ThemeChangedEvent };
            wrapper.RaiseEvent(eventArgs);
        }
        static void TreeWalkerChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            ThemeTreeWalker walker = e.NewValue as ThemeTreeWalker;
            if (walker == null)
                return;
            DependencyObjectWrapper wrapper = new DependencyObjectWrapper(obj);
            if (ShouldExcludeFromTheming(wrapper.Object))
            {
                ExcludeFromTheming(wrapper.Object);
                return;
            }
            ThemeTreeWalker oldWalker = e.OldValue as ThemeTreeWalker;
            string oldThemeName = oldWalker != null ? oldWalker.ThemeName : string.Empty;
            string themeName = walker != null ? walker.ThemeName : string.Empty;
            if (string.IsNullOrEmpty(oldThemeName))
            {
                string typeName = GetTypeNameFromKey(wrapper);
                if (typeName != null)
                {
                    object key = wrapper.GetDefaultStyleKey();
                    ThemedElementsDictionary.RegisterThemeType(string.Empty, typeName, key);
                }
            }
            UpdateDefaultStyleKey(wrapper, themeName);
            if (oldWalker != null && oldWalker.IsTouch)
                RestorePadding(obj);
            UpdatePadding(obj);
        }
        private static void RestorePadding(DependencyObject d)
        {
            d.ClearValue(Control.PaddingProperty);
        }
        static void ExcludeFromTheming(DependencyObject obj)
        {
            SetTreeWalker(obj, null);
        }
        static bool ShouldExcludeFromTheming(DependencyObject obj)
        {
            return obj.GetType() == typeof(System.Windows.Controls.DataGrid);
        }
        static void ThemePropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            Theme newValue = e.NewValue as Theme;
            SetThemeName(obj, newValue != null ? newValue.Name : null);
        }
        static void ThemeNamePropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            string oldThemeName = (string)e.OldValue;
            string rawThemeName = (string)e.NewValue;
            string themeName = GetFromAlias(rawThemeName);
            bool isTouch = IsTouch(rawThemeName);
            obj.ClearValue(GlobalThemeHelper.IsGlobalThemeNameProperty);
            BeforeThemeNameChanged(obj, themeName);
            if (string.IsNullOrEmpty(themeName))
            {
                ClearTreeWalker(obj);
                AfterThemeNameChanged(obj, themeName);
                return;
            }
            if (themeName == Theme.NoneName)
            {
                themeName = string.Empty;
            }
            ChangeTheme(obj, themeName, isTouch, oldThemeName);
            AfterThemeNameChanged(obj, themeName);
        }
        static void BeforeThemeNameChanged(DependencyObject obj, string themeName)
        {
            if (ThemeChanging == null)
                return;
            ThemeChangingRoutedEventArgs eventArgs = new ThemeChangingRoutedEventArgs(themeName) { RoutedEvent = ThemeChangingEvent };
            ThemeChanging(obj, eventArgs);
        }
        static void AfterThemeNameChanged(DependencyObject obj, string themeName)
        {
            if (ThemeChanged == null)
                return;
            ThemeChangedRoutedEventArgs eventArgs = new ThemeChangedRoutedEventArgs(themeName) { RoutedEvent = ThemeChangedEvent };
            ThemeChanged(obj, eventArgs);
        }
        static void ClearTreeWalker(DependencyObject obj)
        {
            SetTreeWalker(obj, new ThemeTreeWalker("", false, obj));
            obj.ClearValue(TreeWalkerProperty);
            if (obj is Window)
            {
                GlobalThemeHelper.Instance.SetWindowsApplicationThemeNameInThread();
            }
        }
        static string GetFromAlias(string themeName)
        {
            if (themeName == Theme.AzureName)
                return Theme.DeepBlueName;
            if (string.IsNullOrEmpty(themeName) || themeName.IndexOf(TouchDelimiter, System.StringComparison.Ordinal) < 0)
                return themeName;
            return themeName.Split(new[] { TouchDelimiter }, StringSplitOptions.RemoveEmptyEntries).First();
        }
        static object lockObject = new object();
        internal static void ChangeTheme(DependencyObject obj, string themeName, bool isTouch, string oldThemeName)
        {
            lock (lockObject)
            {
                ForceThemeKeyCreating(themeName);
                SetTreeWalker(obj, new ThemeTreeWalker(themeName, isTouch, obj));
                SetIsTouchEnabled(obj, isTouch);
            }
        }
        static bool IsTouch(string themeName)
        {
            if (string.IsNullOrEmpty(themeName))
                return false;
            var tokens = themeName.Split(new[] { TouchDelimiter }, StringSplitOptions.RemoveEmptyEntries);
            return tokens.Skip(1).Any(token => token.ToLower() == TouchDefinition);
        }
        static void ForceThemeKeyCreating(string themeName)
        {
            ThemedElementsDictionary.ForceThemeKeysLoading(themeName);
            EnableResourcesForLoadedAssemblies();
        }
        static void UpdateDefaultStyleKey(DependencyObjectWrapper wrapper, string themeName)
        {
            object themeKey = GetThemeOrDefaultKey(themeName, wrapper);
            if (themeKey == null)
                return;
            RaiseThemeNameChanging(wrapper.Object, themeName);
            TraceHelper.Write(TraceSwitch,
                MethodNameString, MethodInfo.GetCurrentMethod().Name,
                ObjectTraceString, wrapper.Object,
                ThemeNameTraceString, themeName,
                KeyTraceString, themeKey);
            wrapper.SetDefaultStyleKey(themeKey);
            RaiseThemeNameChanged(wrapper.Object, themeName);
        }
        static object GetThemeOrDefaultKey(string themeName, DependencyObjectWrapper wrapper)
        {
            if (wrapper.OverridesDefaultStyleKey)
                return null;
            string typeName = GetTypeNameFromKey(wrapper);
            if (typeName == null)
                return null;
            object registeredThemeKey = ThemedElementsDictionary.GetCachedResourceKey(themeName, typeName);
            if (registeredThemeKey != null)
            {
                return registeredThemeKey;
            }
            return ThemedElementsDictionary.GetCachedResourceKey(string.Empty, typeName);
        }
        static string GetTypeNameFromKey(DependencyObjectWrapper wrapper)
        {
            object defaultStyleKey = wrapper.GetDefaultStyleKey();
            string typeName = GetTypeName(defaultStyleKey as Type);
            if (typeName != null)
                return typeName;
            typeName = GetTypeName(defaultStyleKey as DefaultStyleThemeKeyExtension);
            return typeName;
        }
        static string GetTypeName(DefaultStyleThemeKeyExtension key)
        {
            if (key == null)
                return null;
            return key.FullName;
        }
        static string GetTypeName(Type key)
        {
            if (key == null)
                return null;
            return key.FullName;
        }
        #region IThemeManager Members
        void IThemeManager.SetThemeName(DependencyObject d, string value)
        {
            SetThemeName(d, value);
        }
        void IThemeManager.ClearThemeName(DependencyObject d)
        {
            d.ClearValue(ThemeNameProperty);
        }
        string IThemeManager.GetThemeName(DependencyObject d)
        {
            return GetThemeName(d);
        }
        System.Windows.Data.BindingExpression IThemeManager.GetThemeNameBindingExpression(DependencyObject d)
        {
            return new DependencyObjectWrapper(d).GetBindingExpression(d, ThemeManager.ThemeNameProperty);
        }
        #endregion
    }
}
